-- 2022-8-3

---[[10.4.0
    -- 正如我们此前看到的,string.gsub的第三个参数不仅可以是字符串，还可以是一个函数或表
    -- 当为函数是，string.gsub函数会在每次找到匹配时调用该函数，参数是捕获到的内容而返回值则被作为替换字符串
    -- 当为table时，函数会把第一个捕获到的内容作为键，然后将表中对应该键的值作为替换字符串
    -- 如果函数的返回值为nil或表中不包含这个键或表中键的对应值为nil,那么函数gsub不改变这个匹配

    -- 举例，下列函数用于变量展开
    do
        local table1 = {name = "Lua",status = "great"}
        function Expand(s)
            return (string.gsub(s,"%$(%w+)",table1))
        end
        print(Expand("$name is $status, isn't is?"))
    end
    -- 函数gsub根据查找捕获到的键，找对应的值，并用找到的结果替换字符串中相匹配的部分
    -- 如果表中没有对应的键，则不进行替换

    -- 如果不确定是否指定变量具有字符串，那么可以对它们的值调用函数tostring，在这种情况下可以用函数来返回要替换的值
    do
        local _G = {print = 123} -- 一个事先准备好了的表
        function Expand (s)
            return (string.gsub(s,"$(%w+)",function (n)
                return tostring(_G[n])
            end))
        end
        print(Expand("print = $print;a = $a"))
    end
    -- 在函数expand中，对于所有匹配'$(%w+)'的地方，函数gsub都会调用给定的函数，传入捕获到的名字作为参数，并使用返回字符串替换匹配到的内容
    
    -- 最后一个例子，我们回到上一节中提到的格式转换器，现在允许嵌套
    do
        local function toxml(s)
            s = string.gsub(s,"\\(%a+)(%b{})",function (tag,body)
                body = string.sub(body,2,-2)
                body = toxml(body)
                return string.format("<%s>%s</%s>",tag,body,tag)
            end)
            return s
        end

        print(toxml("\\title{The \\bold{big} example}"))
    end
--]]

---[[10.4.1 URL编码
    -- 我们下一个示例中将用到URL编码，也就是HTTP所使用的在URL中传递参数的编码方式
    -- 这种特殊的编码方式会将特殊字符(例如=，&和+)编码为"%xx"的形式，其中xx是对应字符的十六进制
    -- 此外，URL编码还会将空格转换为加号
    --      例如，字符串 "a+b = c"的URL编码为 "a%2Bb+%3D+c"
    -- 最后，URL编码会将每对参数名及其值用等号连接起来，然后每对name = value用&连接起来
    --      例如 name="al" query="a+b = c" q="yes or no"||name='al'&query='a+b = c'&q='yes or no'
    --           对应的URL编码为 "name='al'&query='a%2Bb+%3D+c'&q='yes+or+no'"
    -- 现在，假设要将这个URL解码并将其中的键值对保存到一个表内，以相应的键作为索引，那么使用以下的函数完成基本的解码
    do
        function unescape (s)
            s = string.gsub(s,"+"," ")
            s = string.gsub(s,"%%(%x%x)",function (h)
                return string.char(tonumber(h,16))
            end)
            return s
        end
        print(unescape("a%2Bb+%3D+c"))
        print(unescape("name='al'&query='a%2Bb+%3D+c'&q='yes+or+no'"))
    end
    -- 第一个gsub函数将字符串中的所有加号替换为空格
    -- 第二个gsub函数则匹配所有以百分号开头的两位数十六进制数，并匹配调用一个匿名函数，返回对应的字符

    -- 可以使用函数gmatch来对键值对name=value进行解码，由于键名和值都不能包含&或=,所以可以使用模式'[^&=]+'来匹配它们
    do
        local cgi = {}
        function decode (s)
            for name,value in string.gmatch(s,"([^&=]+)=([^&=]+)") do
                name = unescape(name)
                value = unescape(value)
                cgi[name] = value
            end
        end
        print("---")
        decode("name='al'&query='a%2Bb+%3D+c'&q='yes+or+no'")
        for index, value in pairs(cgi) do
            print(index,value)
        end
    end
    -- 调用函数gmatch会匹配所有格式为name=value的键值对
    -- 对于每组键值对，迭代器会返回对应的捕获(在匹配的字符串中被括号括起来了)，捕获到的内容也就是name,value的值
    -- 循环体内只是简单的对两个字符串调用函数unescape，然后将结果保存到表cgi中

    -- 对应的编码也很容易编写，先写一个escape函数，用它将所有的特殊字符编码为百分号紧跟对应的十六进制形式
    -- (函数format的参数"%02X"用于格式化输出一个两位的十六进制数，若不足两位则以0补齐)，然后把空格替换成加号
    do
        function escape (s)
            s = string.gsub(s,"[&=+%%%c]",function (c)
                return string.format("%%%02X",string.byte(c))
            end)
            s = string.gsub(s," ","+")
            return s
        end
    end

    -- encode函数会遍历整个待编码的表，然后构造出最终的字符串
    do
        function encode (t)
            local b = {}
            for k, v in pairs(t) do
                b[#b+1] = (escape(k) .. "=" .. escape(v))
            end
            return table.concat(b,"&")
        end
        t = {name = "al",query = "a+b = c",q = "yes or no"}
        print(encode(t))
    end

    -- do end 限制局部变量的作用位置
--]]

---[[10.4.2 制表符展开
    -- 在Lua语言中，像'()'这样的空白捕获具有特殊的含义
    -- 该模式并不代表捕获空内容(这样的话毫无意义),而是捕获模式在目标字符串中的位置(该位置是数值)
    print(string.match("hello","()ll()"))
    -- (由于第二个空捕获的位置是在匹配之后，所以这个示例的结果与调用函数string.find得到的结果不一样)
    -- 另一个关于位置捕获的良好示例是在字符串中进行制表符展开
    function expandTabs(s,tab)
        tab = tab or 8 -- 制表符默认大小为8
        local corr = 0
        s = string.gsub(s,"()\t",function (p)
            local sp = tab - (p - 1 + corr)%tab
            corr = corr - 1 + sp
            return string.rep(" ",sp)
        end)
        return s
    end
    -- 函数gsub会匹配字符串中所有的制表符并捕获它的位置
    -- 对于每个制表符，匿名函数会根据其所在位置计算出需要多少个空格才能恰好凑够一列
    -- 该函数先将位置减去1以从0开始计数，然后加上corr凑整之前的制表符(每一个被展开的制表符都会影响后续制表符的位置)
    -- 之后，该函数更新下一个制表符的修正量:为正在被去掉的制表符减1，再加上要增加的空格数sp
    -- 最后，这个函数返回替代制表符的合适数量的字符串

    -- 为了完整起见，让我们再看一下如何实现逆向操作，即将空格转换为制表符
    -- 第一种方法是通过空捕获来对位置进行操作，但还有一种更简单的方法:
    -- 在字符串中每个八个字符插入一个标记，然后将前面有空格的标记替换为制表符
    function unexpandTabs(s,tab)
        tab = tab or 8
        s = expandTabs(s,tab)
        local pat = string.rep(".",tab) -- 辅助模式
        s = string.gsub(s,pat,"%0\1") -- 在每8个字符后添加一个标记\1
        s = string.gsub(s," +\1","\t") -- 将所有以此标记结尾的空格序列 都替换为制表符\t
        s = string.gsub(s,"\1","") -- 将剩下的标记\1删除
        return s
    end
    -- 这个函数首先对字符串进行了制表符展开以移除了其中所有的制表符
    -- 然后构造出一个用于匹配所有8个字符序列的辅助模式，再利用这个模式在每个8个字符后添加一个标记(控制字符\1)
    -- 接着，它将所有以此标记结尾的空格序列都替换为制表符
    -- 最后，将剩下的标记删除(即那些没有位于空格后的标记)
--]]